package com.atguigu.tingshu.common.login.aspect;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.common.constant.PublicConstant;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.Map;

/**
 *
 */
@Aspect
@Component
public class LoginAspect {
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    /**
     * 定义切面逻辑
     */
    /**
     * 前置通知（Before advice）：在某连接点（方法执行等）之前执行，但这个通知不能阻止连接点之前的执行流程（除非它抛出一个异常）。
     * 后置通知（After advice）：在某连接点之后执行，无论该连接点是正常完成还是抛出异常。
     * <p>
     * 返回后通知（After-returning advice）：在某连接点正常完成后执行。
     * <p>
     * 抛出异常后通知（After-throwing advice）：如果方法抛出异常退出，则执行通知。（捕获异常）
     * <p>
     * 环绕通知（Around advice）：在方法调用之前和之后执行，它将决定是否继续执行连接点或直接返回自己的返回值或抛出异常（结果对象和异常皆能获得）。
     */

    //多切面失效
    @Around(value = "@annotation(com.atguigu.tingshu.common.login.annotation.TingshuLogin)")
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {


        // 前置通知逻辑
        //获取用户信息

        //获取请求头中的token 信息
        String jsonWebtoken = getJsonWeb();
        //判断是否携带token通过token去获取用户的载荷
        Long userId = checkTokenAndGetUserId(jsonWebtoken);
        //这里可以将id存入redis中，然后再取出使用，但是由于二者使用的同一个线程所以用新方法

       // ThreadLocal<Long> LongThreadLocal = new ThreadLocal<>();
        //LongThreadLocal.set(userId);//将userId放入线程中

        AuthContextHolder.setUserId(userId);
        Object retVal;//执行目标方法
        try {
            retVal = pjp.proceed();
        } finally {
            AuthContextHolder.removeUserId();//解决内存泄漏
        }

        // 返回通知
        return retVal;
    }

    private Long checkTokenAndGetUserId(String jsonWebtoken) {
        if (StringUtils.isEmpty(jsonWebtoken)) {
            throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
        }
        //携带了令牌，检验令牌（jwt），通过公钥验签,判断是否被篡改过
        Jwt jwt = JwtHelper.decodeAndVerify(jsonWebtoken, new  RsaVerifier(PublicConstant.PUBLIC_KEY));

        String claims = jwt.getClaims();
        Map map = JSONObject.parseObject(claims, Map.class);
        Object userId = map.get("id");
        Object openId = map.get("openid");


        //4验证redis中是否存在jsonWenToken
        String accessTokenKey = RedisConstant.USER_LOGIN_KEY_PREFIX + openId;
        String jsonWenTokenForRedis = stringRedisTemplate.opsForValue().get(accessTokenKey);

        if (StringUtils.isEmpty(jsonWenTokenForRedis) || !jsonWebtoken.equals(jsonWenTokenForRedis)) {
            throw new GuiguException(401, "过期");
        }
        return Long.parseLong(userId.toString());

//        Boolean aBoolean = stringRedisTemplate.hasKey(jsonWebtoken);
//        if (!aBoolean) {
//            throw new GuiguException(401, "过期了");
//        }
//
//        //存在jsontoken
//        return Long.parseLong(userId.toString());
//
//    }
    }
    private static String getJsonWeb() {
        //获取用户目标请求属性对象
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        //获取用户信息
        String token = request.getHeader("token");
        return token;
    }
}

